{
  pkgs,
  lib,
  stdenv,
  fetchzip,
  nixosTests,
}:

let

  buildGnomeExtension =
    {
      # Every gnome extension has a UUID. It's the name of the extension folder once unpacked
      # and can always be found in the metadata.json of every extension.
      uuid,
      name,
      pname,
      description,
      # extensions.gnome.org extension URL
      link,
      # Extension version numbers are integers
      version,
      sha256,
      # Hex-encoded string of JSON bytes
      metadata,
    }:

    stdenv.mkDerivation {
      pname = "gnome-shell-extension-${pname}";
      version = toString version;
      src = fetchzip {
        url = "https://extensions.gnome.org/extension-data/${
          builtins.replaceStrings [ "@" ] [ "" ] uuid
        }.v${toString version}.shell-extension.zip";
        inherit sha256;
        stripRoot = false;
        # The download URL may change content over time. This is because the
        # metadata.json is automatically generated, and parts of it can be changed
        # without making a new release. We simply substitute the possibly changed fields
        # with their content from when we last updated, and thus get a deterministic output
        # hash.
        postFetch = ''
          echo "${metadata}" | base64 --decode > $out/metadata.json
        '';
      };
      nativeBuildInputs = with pkgs; [ buildPackages.glib ];
      buildPhase = ''
        runHook preBuild
        if [ -d schemas ]; then
          glib-compile-schemas --strict schemas
        fi
        runHook postBuild
      '';
      installPhase = ''
        runHook preInstall
        mkdir -p $out/share/gnome-shell/extensions/
        cp -r -T . $out/share/gnome-shell/extensions/${uuid}
        runHook postInstall
      '';
      meta = {
        description = builtins.head (lib.splitString "\n" description);
        longDescription = description;
        homepage = link;
        license = lib.licenses.gpl2Plus; # https://gjs.guide/extensions/review-guidelines/review-guidelines.html#licensing
        platforms = lib.platforms.linux;
        maintainers = [ lib.maintainers.honnip ];
      };
      passthru = {
        extensionPortalSlug = pname;
        # Store the extension's UUID, because we might need it at some places
        extensionUuid = uuid;

        tests = {
          gnome-extensions = nixosTests.gnome-extensions;
        };
      };
    };
in
lib.makeOverridable buildGnomeExtension
